/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ufpb.compiladores.sintatico.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Stack;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import ufpb.compiladores.lexico.LexicalTable;
import ufpb.compiladores.lexico.LexicalTable.Entry;
import ufpb.compiladores.sintatico.Clause;
import ufpb.compiladores.sintatico.Entity;
import ufpb.compiladores.sintatico.Enviroment;
import ufpb.compiladores.sintatico.Production;
import ufpb.compiladores.sintatico.SyntacticalAnalyzer;

/**
 *
 * @author SemiNick
 */
public class SyntacticalAnalyzerRecursiveImpl implements SyntacticalAnalyzer {

    private final Enviroment env;
    private final LexicalTable lt;
    
    //private HashMap<String, String> types = new HashMap<String, String>();
    
    ArrayList<HashMap<String, String>> types = new ArrayList<HashMap<String,String>>(); 
    private String err_msg = null;
    private int nh = 0;
    private boolean atr = false;
    private boolean iw = false;

    public SyntacticalAnalyzerRecursiveImpl(Enviroment env, LexicalTable lt) {

       if (env == null) {

           throw new NullPointerException("Enviroment cannot be null");
       } else if (lt == null) {

           throw new NullPointerException("LexicalTable cannot be null");
       }

       this.env = env;
       this.lt = lt;
    }

    @Override
    public void processEnviroment(Document doc, Element r) {

        if (processProduction(this.env.getInitialProduction(), lt, new Stack<String>(), 0, new Stack<String>(), null)) {

            //System.out.println("ERROR: " + err_msg);
            if (this.lt.getNext(1) != null) {

                System.out.println("ERROR: tokens after end");
            }
        }
    }
    
    public void assignType (LexicalTable l, Stack<String> ts){
        
        int i = -1;

        int k=0;
            int quantos = 0;
            HashMap<String, String> temp = new HashMap<String, String>();

            if (ts.size() > 0){

            for ( ; k<=ts.size() -1; k++){

                if (ts.get(k).equals("$"))
                    quantos++;
            }
        }

        while (l.getNext(i).getDescription().equalsIgnoreCase("Identificador")){

            //se for um novo escopo cria um novo hashmap
            //se não, se ja tiver, nao faz nada. se nao tiver, adiciona.

            if (quantos == 1){

                if (types.isEmpty()){
                    temp.put(l.getNext(i).getToken() , l.getNext(1).getToken());
                    this.types.add(temp);
                } else {

                    this.types.get(0).put(l.getNext(i).getToken() , l.getNext(1).getToken());
                }

            }
            else if(quantos >= 2)
            { //ta no novo escopo

                //cria novo hash e adiciona ao array

                temp.put(l.getNext(i).getToken(), l.getNext(1).getToken());
                this.types.add(temp);
                nh++;

            } 
            i--;

        }
    }

    public boolean checkTypes(Stack<String> pct1, int op) {

        String resultado =  null;
        if (pct1.size() >= 2){
        if (op == 0){ //operação relacional

            
            if (pct1.peek() == null || (pct1.get(pct1.size() - 2) == null)){
                                            pct1.pop();
                            pct1.pop();
                            return false;
            }
            else if(pct1.peek().equalsIgnoreCase("integer") && (pct1.get(pct1.size() - 2).equalsIgnoreCase("integer")))
            {

                resultado = "integer";
                } else if (pct1.peek().equalsIgnoreCase("integer") && (pct1.get(pct1.size()-2).equalsIgnoreCase("real"))){
                  
                    resultado = "real";
                    } else if (pct1.peek().equalsIgnoreCase("real") && (pct1.get(pct1.size()-2).equalsIgnoreCase("integer"))){
                      
                        resultado = "real";
                        } else if (pct1.peek().equalsIgnoreCase("real") && (pct1.get(pct1.size()-2).equalsIgnoreCase("real"))){
                        
                            resultado = "real";
                        } else {
                            pct1.pop();
                            pct1.pop();
                            return false;
                        }
            
            pct1.pop();
            pct1.pop();
            pct1.push(resultado);
            return true;

        } else if (op == 1){ //operação lógica
            if (pct1.peek() == null || (pct1.get(pct1.size() - 2) == null)){
                            pct1.pop();
                            pct1.pop();
                            return false;
            }
            else if (pct1.peek().equalsIgnoreCase("boolean") && pct1.get(pct1.size()-2).equalsIgnoreCase("boolean")){

                pct1.pop();
                pct1.pop();
                pct1.push("boolean");
                return true;
            }

        } else if (op == 2){
            if (pct1.peek() == null || (pct1.get(pct1.size() - 2) == null)){
                            pct1.pop();
                            pct1.pop();
                            return false;
            } else if (pct1.peek().equalsIgnoreCase("integer") && pct1.get(pct1.size()-2).equalsIgnoreCase("integer") ||
                (pct1.peek().equalsIgnoreCase("integer") && pct1.get(pct1.size()-2).equalsIgnoreCase("real") ||
                (pct1.peek().equalsIgnoreCase("real") && pct1.get(pct1.size()-2).equalsIgnoreCase("integer") ||
                (pct1.peek().equalsIgnoreCase("real") && pct1.get(pct1.size()-2).equalsIgnoreCase("real"))))){

                    pct1.pop();
                    pct1.pop();
                    pct1.push("boolean");
                    return true;
            }

        } else if (pct1.peek().equalsIgnoreCase("program") || pct1.get(pct1.size()-2).equalsIgnoreCase("program")){

                        this.err_msg = "Attempt to use the program id "+lt.getCurrent().getToken()
                        +" as a variable or a procedure id at line "+lt.getCurrent().getLine()+".";
                        System.out.println("ERROR " + this.err_msg);
            pct1.pop();
            pct1.pop();
             return false;
            }
        
        pct1.pop();
        pct1.pop();
        return false;
        }

        return false;
    }

    public boolean checkAssignment (String rec, String ret){

        if (rec.equalsIgnoreCase(ret)){

            return true;
        } else if (rec.equalsIgnoreCase("real") && ret.equalsIgnoreCase("integer")){

            return true;
        } else if (rec.equals("program")){

            return false;
            }

        return false;
    }

    private boolean processProduction(Production p, LexicalTable lt, Stack<String> ts, int x, Stack<String> pct, Entry rec) {

        int ci;
        int ei;
        boolean ok = true;
        boolean passouOp = false;
        Clause[] clauses = p.getClauses();
        
        //Stack<String> pct = new Stack<String>();
        //Entry rec = null;

        laco_c:
        for (ci = 0; ci < clauses.length; ci++) {

            ok = true;
            Clause c = clauses[ci];
            Entity[] ents = c.getEntities();
            laco_e:
            for (ei = 0; ei < ents.length; ei++) {

                Entity e = ents[ei];
                if (lt.getNext(1).getToken().equals("33")) {
                    
                    System.out.println("fhada");
                }
                switch (e.getType()) {

                    case PRODUCTION:

                        if (!this.processProduction(
                                this.env.getProduction(e.value()), lt, ts, x, pct, rec)) {

                            ok = false;
                            //continue laco_c;

                            break laco_e;
                        }

                        break;

                    case TOKEN:

                        if (lt.getNext(1) != null) {

                            Entry et = lt.getNext(1);
                            if (!et.getToken().equalsIgnoreCase(e.value())) {

                                this.err_msg = "Expecting: " + e.value() + " but "
                                                + et.getToken() + " found at line: " + et.getLine();
                                if (ei == ents.length && !p.isOptional()) {

                                    System.out.println("Expecting: " + e.value() + " but "
                                        + et.getToken() + " found at line: " + et.getLine());
                                }
                                ok = false;
                                //continue laco_c;
                                break laco_e;
                            } else {
                                
                                this.lt.next();

                                if (lt.getCurrent().getToken().equals(":")){

                                    assignType(lt, ts);
                                } else if(lt.getCurrent().getToken().equalsIgnoreCase("if") ||
                                        lt.getCurrent().getToken().equalsIgnoreCase("while")){

                                    iw = true;
                                } else if (lt.getCurrent().getToken().equalsIgnoreCase(")") &&
                                        ((lt.getNext(1).getToken().equals("do") || lt.getNext(1).getToken().equalsIgnoreCase("then")))){

                                    iw = false;
                            }else  if (lt.getCurrent().getToken().equals("(") && iw) {

                                    int l = 1;
                                    boolean opiw = false;

                                    while (!lt.getNext(l).getToken().equalsIgnoreCase("do") &&
                                            !lt.getNext(l).getToken().equalsIgnoreCase("then")){

                                        if (lt.getNext(l).getToken().equalsIgnoreCase("true") ||
                                                lt.getNext(l).getToken().equalsIgnoreCase("false")){

                                            if ((!opiw && lt.getNext(l+1).getDescription().contains("Operador")) ||
                                            lt.getNext(l+1).getToken().equals(")")){
                                                pct.push("boolean");
                                            } else {
                                                opiw = false;
                                            }
                                        } else if (lt.getNext(l).getDescription().equalsIgnoreCase("Número inteiro")){

                                            if ((!opiw && lt.getNext(l+1).getDescription().contains("Operador")) ||
                                            lt.getNext(l+1).getToken().equals(")")){
                                                pct.push("integer");
                                            } else {
                                                opiw = false;
                                            }
                                        } else if (lt.getNext(l).getDescription().equals("Número real")){

                                            if ((!opiw && lt.getNext(l+1).getDescription().contains("Operador")) ||
                                            lt.getNext(l+1).getToken().equals(")")){
                                                pct.push("real");
                                            } else {
                                                opiw = false;
                                            }
                                        } else if (lt.getNext(l).getDescription().equalsIgnoreCase("Identificador") &&
                                            !lt.getNext(l).getToken().equalsIgnoreCase(ts.get(ts.size()-1))) {

                                            int a = nh;
                                            for ( ; a>0; a--){

                                                if (types.get(a).containsKey(lt.getNext(l).getToken()))
                                                    break;
                                            }

                                         if ((!opiw && lt.getNext(l+1).getDescription().contains("Operador"))
                                                ||
                                            lt.getNext(l+1).getToken().equals(")")){
                                                pct.push(types.get(a).get(lt.getNext(l).getToken()));
                                            } else {
                                                opiw = false;
                                            }

                                        } else if (lt.getNext(l).getDescription().contains("Operador")){

                                            opiw = true;

                                        if (lt.getNext(l+1).getToken().equalsIgnoreCase("true") ||
                                                lt.getNext(l+1).getToken().equalsIgnoreCase("false")){
                                            pct.push("boolean");
                                        }
                                        else if (lt.getNext(l+1).getDescription().equalsIgnoreCase("Número inteiro")){

                                            pct.push("integer");

                                    } else if (lt.getNext(l+1).getDescription().equals("Número real")){

                                            pct.push("real");

                                    } else if (lt.getNext(l+1).getDescription().equalsIgnoreCase("Identificador") &&
                                        !lt.getNext(l+1).getToken().equalsIgnoreCase(ts.get(ts.size()-1))){

                                        int a = nh;
                                        for ( ; a>0; a--){

                                            if (types.get(a).containsKey(lt.getNext(l+1).getToken()))
                                                break;
                                        }

                                            pct.push(types.get(a).get(lt.getNext(l+1).getToken()));
                                        }
                                            
                                    if (lt.getNext(l).getDescription().contains("Operador") &&
                                        lt.getNext(l).getDescription().contains("Lógico")){

                                        if(!checkTypes(pct, 1))
                                        {
                                            this.err_msg = "Incompatible types at line: " + et.getLine();
                                            System.out.println(this.err_msg);
                                        }
                                    } else if (lt.getNext(l).getDescription().contains("Operador") &&
                                        lt.getNext(l).getDescription().contains("relacional")){

                                        if(!checkTypes(pct, 2))
                                        {
                                            this.err_msg = "Incompatible types at line: " + et.getLine();
                                            System.out.println(this.err_msg);
                                        }
                                    } else if (lt.getNext(l).getDescription().contains("Operador") &&
                                        !lt.getNext(l).getDescription().contains("Lógico")){

                                        if(!checkTypes(pct, 0))
                                        {
                                            this.err_msg = "Incompatible types at line: " + et.getLine();
                                            System.out.println(this.err_msg);
                                        }
                                    }
                                       
                                    }
                                         l++;
                                   }
                             } else if (lt.getCurrent().getToken().equalsIgnoreCase("begin")) {

                                    x++;
                                } else if (lt.getCurrent().getToken().equalsIgnoreCase("end")) {
                                    x--;

                                        if (x==0){

                                            if (!ts.isEmpty()){
                                                while( !ts.peek().equals("$")){

                                                ts.pop();
                                            }

                                            int h = nh;
                                            for (int t=0; t<h; t++){

                                                this.types.remove(types.size()-1);
                                                nh--;
                                            }
                                            ts.pop();
                                            }
                                        }

                                } else if ((lt.getCurrent().getToken().equalsIgnoreCase("program"))) {

                                    HashMap<String, String> temp = new HashMap<String, String>();
                                    temp.put(lt.getNext(1).getToken(),"program");
                                    types.add(temp);
                                    ts.push("$");
                                   } else if (lt.getCurrent().getToken().equalsIgnoreCase("true")
                                        || lt.getCurrent().getToken().equalsIgnoreCase("false")){

                                        pct.push("boolean");
                            }else  if (lt.getCurrent().getToken().equals(":=")) {

                                    atr = true;
                                    rec = lt.getPrevious();
                                    if (lt.getNext(2).getToken().equals(";")){

                                        passouOp = true;

                                        String retorno = null;

                                        if (lt.getNext(1).getDescription().equalsIgnoreCase("Número inteiro")){
                                            retorno = "integer";
                                        } else if (lt.getNext(1).getDescription().equalsIgnoreCase("Número real")){
                                            retorno = "real";
                                        } else if (lt.getNext(1).getDescription().equalsIgnoreCase("Identificador")&&
                                            !lt.getNext(1).getToken().equalsIgnoreCase(ts.get(ts.size()-1))){
                                            
                                            int a = nh;
                                            for ( ; a>0; a--){

                                                if (types.get(a).containsKey(lt.getNext(1).getToken()))
                                                    break;
                                            }
                                            retorno = types.get(a).get(lt.getNext(1).getToken());
                                        } else if (lt.getNext(1).getToken().equalsIgnoreCase("true") ||
                                                lt.getNext(1).getToken().equals("false")){
                                            retorno = "boolean";
                                        }

                                        int a = nh;
                                            for ( ; a>=0; a--){

                                                if (types.get(a).containsKey(rec.getToken()))
                                                    break;
                                            }
                                        if (a>=0){
                                        if (null == types.get(a).get(rec.getToken())){

                                            this.err_msg = "Illegal assignment at line  " + et.getLine()+".";
                                            System.out.println(this.err_msg);
                                       }
                                       else if (rec != null && (!pct.isEmpty() || lt.getNext(2).getToken().equals(";"))){
                                        if (!checkAssignment(types.get(a).get(rec.getToken()), retorno)) {

                                            this.err_msg = "Illegal assignment at line  " + et.getLine()+". Please!";
                                            System.out.println(this.err_msg);
                                       }
                                       rec = null;
                                        }
                                    }
                                        
                                    }
                            } else if (lt.getCurrent().getDescription().equalsIgnoreCase("Número inteiro")){// DAQUI

                                if (!passouOp) {
                                    if (lt.getNext(1).getDescription().contains("Operador")){
                                        pct.push("integer");
                                    }
                                } else {

                                    passouOp = false;
                                }

                            } else if (lt.getCurrent().getDescription().equalsIgnoreCase("Número real")){// DAQUI

                                if (!passouOp) {
                                    if (lt.getNext(1).getDescription().contains("Operador")){
                                        pct.push("real");
                                    }

                                } else {
                                    passouOp = false;
                                }

                            } else if (lt.getCurrent().getToken().equalsIgnoreCase("true") ||
                                        lt.getCurrent().getToken().equalsIgnoreCase("false")){

                                    if (!passouOp) {
                                    if (lt.getNext(1).getDescription().contains("Operador")){
                                        pct.push("boolean");
                                    }

                                } else {
                                    passouOp = false;
                                }

                            } else if (lt.getCurrent().getDescription().contains("Operador")) {

                        if (!iw){

                                    passouOp = true;

                                    if (lt.getNext(1).getDescription().equalsIgnoreCase("Número inteiro")){


                                        pct.push("integer");
                                    } else if (lt.getNext(1).getDescription().equalsIgnoreCase("Número real")){
                                    
                                        pct.push("real");
                                    } else if (lt.getNext(1).getDescription().equalsIgnoreCase("Identificador") &&
                                            !lt.getNext(1).getToken().equalsIgnoreCase(ts.get(ts.size()-1)))/* &&
                                            types.get(lt.getNext(1).getToken()) != null)*/{
                                  int a = nh;
                                    for ( ; a>0; a--){

                                        if (types.get(a).containsKey(lt.getNext(1).getToken()))
                                            break;
                                    }
                                        if (a != 0 || (a == 0 && types.get(0).containsKey(lt.getNext(1).getToken())))
                                            pct.push(types.get(a).get(lt.getNext(1).getToken()));
                                        else
                                            pct.push("null");

                                    } else if (lt.getNext(1).getToken().equalsIgnoreCase("true") ||
                                        lt.getNext(1).getToken().equalsIgnoreCase("false")){// ||
                                            //types.get(a).get(lt.getNext(1).getToken()).equalsIgnoreCase("boolean")){

                                        pct.push("boolean");
                                    }
                                

                                    if (lt.getCurrent().getDescription().contains("Operador") &&
                                        lt.getCurrent().getDescription().contains("Lógico")){

                                        if(!checkTypes(pct, 1))
                                    {

                                        this.err_msg = "Incompatible types at line: " + et.getLine();
                                        System.out.println(this.err_msg);
                                    } //else {
                                        int b = nh;
                                    for ( ; b>=0; b--){

                                        if (types.get(b).containsKey(rec.getToken()))
                                            break;
                                    }
                                    if (atr){

                                       if (null == types.get(b).get(rec.getToken())){
                                           
                                            this.err_msg = "Illegal assignment at line  " + et.getLine()+".";
                                            System.out.println(this.err_msg);
                                       }

                                       else if (rec != null && !pct.isEmpty()){
                                        if (!checkAssignment(types.get(b).get(rec.getToken()), pct.peek())) {

                                            this.err_msg = "Illegal assignment at line  " + et.getLine()+".";
                                            System.out.println(this.err_msg);

                                        }
                                    }

                                       atr = false;
                                    }
                                
                                } else if (lt.getCurrent().getDescription().contains("Operador") &&
                                        lt.getCurrent().getDescription().contains("relacional")){

                                        if(!checkTypes(pct, 2))
                                    {

                                        this.err_msg = "Incompatible types at line: " + et.getLine();
                                        System.out.println(this.err_msg);
                                    } //else {
                                        int b = nh;
                                    for ( ; b>=0; b--){

                                        if (types.get(b).containsKey(rec.getToken()))
                                            break;
                                    }
                                    if (atr){

                                       if (null == types.get(b).get(rec.getToken())){

                                            this.err_msg = "Illegal assignment at line  " + et.getLine()+".";
                                            System.out.println(this.err_msg);
                                       }

                                       else if (rec != null && !pct.isEmpty()){
                                        if (!checkAssignment(types.get(b).get(rec.getToken()), pct.peek())) {

                                            this.err_msg = "Illegal assignment at line  " + et.getLine()+".";
                                            System.out.println(this.err_msg);

                                        }
                                    }

                                       atr = false;
                                    }

                                } else if (lt.getCurrent().getDescription().contains("Operador") &&
                                        !lt.getCurrent().getDescription().contains("Lógico")){


                                    int b = nh;
                                    for ( ; b>=0; b--){

                                        if (types.get(b).containsKey(rec.getToken()))
                                            break;
                                    }
                                    if(!checkTypes(pct, 0))
                                    {

                                        this.err_msg = "Incompatible types at line: " + et.getLine();
                                        System.out.println(this.err_msg);
                                    } //else {
                                    if (atr){
                                    if (null == types.get(b).get(rec.getToken())){

                                            this.err_msg = "Illegal assignment at line  " + et.getLine()+".";
                                            System.out.println(this.err_msg);
                                       }


                                 if (rec != null && !pct.isEmpty() ) {

                                        if (!checkAssignment(types.get(b).get(rec.getToken()), pct.peek())){

                                            this.err_msg = "Illegal assignment at line  " + et.getLine()+".";
                                            System.out.println(this.err_msg);
                                        }
                                        //}
                                    }
                                    atr = false;
                                    }
                                    
                                }
                                    rec = null;
                            } //iw = false;
                                
                            }
                            } 
                        } else {

                            this.err_msg = "Expecting: " + e.value() + " but nothing found";
                            ok = false;
                            break laco_e;
                        }
                        break;

                    case TYPE:

                        if (lt.getNext(1) != null) {

                            Entry et = lt.getNext(1);
                            if (!et.getDescription().equalsIgnoreCase(e.value())) {

                                this.err_msg = "Expecting: " + e.value() + " but "
                                        + et.getToken() + " found at line: " + et.getLine();
                                if (ei == ents.length && !p.isOptional()) {

                                    System.out.println("ERRORi: " + "Expecting: " + e.value() + " but "
                                        + et.getToken() + " found at line: " + et.getLine());
                                }
                                ok = false;
                                //continue laco_c;
                                break laco_e;
                            } else {

                                this.lt.next();

                                if (lt.getCurrent().getDescription().equals("Identificador")){

                                        if(x == 0)   {

                                            boolean jaTem = false;

                                            int aux = ts.size()-1;

                                            while (!ts.get(aux).equals("$")){

                                                if (ts.get(aux).equals(lt.getCurrent().getToken()))
                                                    jaTem = true;
                                                aux--;
                                            }

                                            if (jaTem){

                                                this.err_msg = "Variable or procedure id "+lt.getCurrent().getToken()+
                                                        " already declared in the same scope at line "+lt.getCurrent().getLine()+".";
                                                System.out.println("ERROR " + this.err_msg);
                                            } else {

                                                ts.push(lt.getCurrent().getToken());

                                                }
                                           
                                                if (lt.getPrevious().getToken().equalsIgnoreCase("procedure")){

                                                    ts.push("$");
                                                    types.get(0).put(lt.getCurrent().getToken(), "procedure");
                                                }

                                        } else {

                                            if (ts.size()-1 == ts.search(lt.getCurrent().getToken())){

                                                this.err_msg = "Attempt to use the program id "+lt.getCurrent().getToken()
                                                        +" as a variable or a procedure id at line "+lt.getCurrent().getLine()+".";
                                                        System.out.println("ERROR " + this.err_msg);

                                                } else if (!ts.contains(lt.getCurrent().getToken())){

                                                        this.err_msg = "Undeclared variable "+lt.getCurrent().getToken()+
                                                                " at line "+lt.getCurrent().getLine()+".";
                                                        System.out.println("ERROR " + this.err_msg);
                                                    }
                                        }

//                                        if (p.getName().equalsIgnoreCase("fator")){
//
//                                            pct.push(this.types.get(lt.getCurrent().getToken()));
//                                        }

                                      int a = types.size()-1;
                                            for ( ; a>=0; a--){

                                                if (types.get(a).containsKey(lt.getCurrent().getToken()))
                                                    break;
                                            }
                                    if (!passouOp) {
                                                if (lt.getNext(1).getDescription().contains("Operador")){// &&
                                                        //this.types.get(lt.getCurrent().getToken()) != null){
                                                    if (ts.contains(lt.getCurrent().getToken())){
                                                        pct.push(this.types.get(a).get(lt.getCurrent().getToken()));
                                                    } else {

                                                        pct.push("null");
                                                    }
                                                } //mudar dps nao declarada na operacao como operando mudar tb qd acha operador
                                            } else {
                                                passouOp = false;
                                            }

                                }

                            }
                            
                        } else {

                            this.err_msg = "Expecting: " + e.value() + " but nothing found";
                            ok = false;
                            break laco_e;
                        }
                        break;

                    case VOID:

                        break;
                }
            }

            //Processou todas as entidades sem dar erro então já processamos a produção
            if (ok) {

                return true;
            }
        }

        //Gerou erro, mas produção não é opcional
        if (!ok && !p.isOptional() && ci == clauses.length - 1) {

            System.out.println("ERROR " + this.err_msg);
        }

        return ok;
    }
}
